#!coding:utf-8
import logging,os
from django.db.models import Q
import netaddr
from cvirt.common import exception
from cvirt.common import config, utils,constant
from cvirt.cnode.network import bridge as linux_net
from cvirt.cnode.libvirtXmlObject.network import NATNetwork, IsolateNetwork,BridgeNetwork
from cvirt.manager.models import Network, FixedIp, Instance,Interface, Server
from cvirt.cnode.connection import get_connection
from cvirt.cnode.nwfilter import api as nwapi
LOG = logging.getLogger('cnode.network.api')

conf = config.load_settings()

def allocate_fixed_ip(interface, address=None,update_dhcp=True):
    '''分配fixed ip，返回可用的fixed ip
    在创建实例之前调用
    '''
    if interface:
        query_params = dict(allocated=False,network=interface.network)
        if address:
            query_params.update(address=address)

        fixedip = FixedIp.objects.first(**query_params)
        if fixedip and interface:
            fixedip.update(dict(allocated=True))
            interface.update(dict(fixedip=fixedip))
            if update_dhcp:
                linux_net.update_dhcp(fixedip.network,interface.instance.host.ip)
            return fixedip
        else:
            raise exception.FixedIpNoMoreForInstance()


def unallocate_fixed_ip(interface):
    "释放fixed ip"
    if interface and interface.fixedip:
        LOG.debug('unallocate fix ip %s for interface %s' % (interface.fixedip.address,interface.name))
        fixed_ip = interface.fixedip
        fixed_ip.update(dict(allocated=False,leased=False))
        interface.update(dict(fixedip=None))
        linux_net.release_dhcp(fixed_ip.network.bridge, fixed_ip.address, interface.mac)


def init_networks():
    #todo
    #网络修改后，需要比对差异，或者先删除后添加
    LOG.info('init network...')
    host = Server.objects.get_or_none(ip=conf['cnode.host'],cluster__isnull=False)
    if host:
        for n in Network.objects.all_with_deleted().filter(cluster=host.cluster):

            try:
                if n.get_deleted():
                    delete_network(n)
                    LOG.debug("remove network %s for init" % n.name)
                else:
                    LOG.debug("init network %s for init" % n.name)
                    if n.networktype==constant.BRIDGE or n.networktype==constant.VLAN:
                        if n.networktype == constant.BRIDGE:
                            if not find_network(n.libvirt_name):
                                create_bridge_network(n)
                        elif n.networktype == constant.VLAN:
                            if not find_network(n.libvirt_name):
                                create_vlan_network(n)

                        #如果bridge和vlan网络需要重建网桥和vlan网卡
                        interface = linux_net.ensure_vlan(n) if n.networktype == constant.VLAN else conf['bond.name']
                        linux_net.ensure_bridge(n,interface)
                        if n.rundhcp:
                            linux_net.update_dhcp(n,conf['cnode.host'])
                    elif n.networktype == constant.NAT:
                        if not find_network(n.libvirt_name):
                            create_nat_network(n)
                    elif n.networktype == constant.ISOLATE:
                        if not find_network(n.libvirt_name):
                            create_isolate_network(n)
            except Exception as e:
                LOG.warn("init network failed,exception is :\n%s" % e )


def init_fixedips(network_ref):
    if network_ref.rundhcp:
        LOG.info(
            'append fixedip <%s,%s> to network %s ' % (network_ref.dhcp_start, network_ref.dhcp_end, network_ref.name))
        for ip in netaddr.IPRange(network_ref.dhcp_start, network_ref.dhcp_end):
            #.0和.1我们保留
            if not str(ip).endswith('.0'):
                #避免重复插入数据,每个node都要执行一遍
                if not FixedIp.objects.filter(address=str(ip), network=network_ref):
                    FixedIp.objects.create(address=str(ip), network=network_ref)


def _create_bridge_net(interface, network_ref, update):
    if not update:
        LOG.info('create bridge network %s' % network_ref.name)
        linux_net.ensure_bridge(network_ref, interface)
        network = BridgeNetwork(name=network_ref['libvirt_name'], bridge=network_ref['bridge'])
        _apply_network(network_ref, network)


def create_bridge_network(network_ref,interface=conf['bond.name'],update=False):
    if not network_ref or network_ref['networktype'] != constant.BRIDGE:
        raise exception.NetworkInitFailure()
    _create_bridge_net(interface, network_ref, update)

def refresh_interfaces(network_ref):
    '''
    1.#网络修改后刷新本机和网络关联的网卡规则（dhcpserver）
    2.#分配ip，桥接网络重启启用dhcp后，自动给关联的网卡分配一个ip(bridge,vlan)
    3.给主机分配ip(bridge,vlan)
    @param network_ref:
    @return:
    '''
    # TODO wired,链式调用过程中，network ip出现获取到同一条记录的问题，可能是事务没有提交
    # TODO 为了避免事务提交问题，每个node都为所有的host分配network ip，虽然会重复执行这个过程，但是可以保证数据正确性
    if network_ref.rundhcp and network_ref.networktype in [2,3]:
        for host in Server.objects.filter(cluster=network_ref.cluster,status=2):
            linux_net.allocate_ip_for_network(network_ref,host.ip)

    for iface in network_ref.interface_set.filter(instance__host__ip=conf['cnode.host']):
        nwapi.refresh_filter_for_interface(iface)
        if network_ref.rundhcp and network_ref.networktype in [2,3]:
            allocate_fixed_ip(iface,update_dhcp=False)


def remove_fixedips(network_ref):
    for ip in network_ref.fixedip_set.all():
        ip.remove()

def update_network(network_ref,interface=conf['bond.name'],last_cidr=None):
    #在remove fixedip和refresh_interfaces之前获取
    #获取最后一次已删除的network ip
    last_network_ip = FixedIp.objects.all_with_deleted().order_by('-id').\
        filter(host=conf['cnode.host'],deleted_at__isnull=False)

    last_network_ip = last_network_ip[0] if last_network_ip else None

    #create_bridge_network(network_ref,interface,update=True)
    LOG.info('restart dhcp for network %s ' % network_ref.name)
    refresh_interfaces(network_ref)
    linux_net.restart_network(network_ref,last_network_ip=last_network_ip,last_cidr=last_cidr)


def update_bridge_network(network_ref,interface=conf['bond.name'],last_cidr=None):
    LOG.info('update fixedip for bridge network %s ' % network_ref.name)
    update_network(network_ref,interface,last_cidr=last_cidr)


def update_vlan_network(network_ref,interface=conf['bond.name'],last_cidr=None):
    LOG.info('update fixedip for vlan network %s ' % network_ref.name)
    update_network(network_ref,interface,last_cidr=last_cidr)

def create_vlan_network(network_ref,update=False):
    if not network_ref or network_ref['networktype'] != constant.VLAN:
        raise exception.NetworkInitFailure()
    if not update:
        LOG.info('create vlan bridge network %s' % network_ref.name)
        vlaninterface = linux_net.ensure_vlan(network_ref)
    if vlaninterface:
        _create_bridge_net(vlaninterface, network_ref, update)


def find_network(network_name):
    try:
        return get_connection().networkLookupByName(network_name)
    except:
        pass


def cast_snoop(netdomain,flag):
    bridge = netdomain.bridgeName()
    configpath = "/sys/devices/virtual/net/%s/bridge/multicast_snooping" % bridge
    if os.path.exists(configpath):
        LOG.debug("edit bridge %s multicast_snooping to %s" % (bridge,int(flag)))
        with file(configpath,'w') as f:
            f.write(str(int(flag)))
    else:
        LOG.warn('can not found bridge multicast_snooping config file')


def _apply_network(network_ref,network):
    domain = find_network(network_ref['libvirt_name'])
    if domain:
        LOG.warn('network %s exists, we will stop it' % network_ref['name'])
        if domain.isActive():
            domain.destroy()

    xml = network.to_xml(pretty_print=False)
    domain = get_connection().networkDefineXML(xml)
    LOG.info("set network %s auto start" % network_ref.name)
    domain.setAutostart(1)
    domain.create()
    LOG.info('create %s network %s succesful' % (network_ref.networktype,network_ref.name))

    cast_snoop(domain,network_ref.cast_snoop)

    return network_ref.name


def _create_internal_network(network_ref, type):
    if not network_ref or network_ref['networktype'] != type: #0-nat
        raise exception.NetworkInitFailure('network_ref is none or network type is not %s' % type)
    if type == 0:
        networkclass = NATNetwork
    elif type == 1:
        networkclass = IsolateNetwork

    network = networkclass(name=network_ref['libvirt_name'], stp=network_ref['stp'], delay=network_ref['delay'],
                           netmask=network_ref['netmask'], address=network_ref['address'])
    if network_ref['dev']:   #isolate network do not have dev
        network.dev = network_ref['dev']
    if network_ref['rundhcp']:
        LOG.info("network %s has dhcp <%s,%s>" % (network_ref.name,network_ref['dhcp_start'],network_ref['dhcp_end']))
        network.dhcp_start = network_ref['dhcp_start']
        network.dhcp_end = network_ref['dhcp_end']

    return _apply_network(network_ref,network)


def create_nat_network(network_ref):
    """

    @param network_ref:
    """
    LOG.info('create nat network %s' % network_ref.name)
    return _create_internal_network(network_ref, constant.NAT)


def create_isolate_network(network_ref):
    return _create_internal_network(network_ref, constant.ISOLATE)


def update_nat_network(network_ref):
    """

    @param network_ref:
    """
    delete_network(network_ref)
    #nat网络修改后不需要刷新网卡规则，nat dhcp是强制开启的
    return _create_internal_network(network_ref, constant.NAT)


def update_isolate_network(network_ref):
    """

    @param network_ref:
    """

    delete_network(network_ref)
    result = _create_internal_network(network_ref, constant.ISOLATE)
    refresh_interfaces(network_ref)
    return result


def update_dhcp(network_ref,host):
    '''
    初始化dhcp，每次网络变动调用
    '''
    linux_net.update_dhcp(network_ref,host)


def lease_fixed_ip(address):
    """Called by dhcp-bridge when ip is leased."""
    LOG.debug('Leased IP |%s|' % address)
    fixed_ip = FixedIp.objects.get(address=address)

    if not fixed_ip.interface:
        raise Exception('IP %s leased that is not associated' %
                        address)
    fixed_ip.update(dict(leased=True))
    if not fixed_ip.allocated:
        LOG.warn('IP |%s| leased that isn\'t allocated', address)


def release_fixed_ip(address):
    """Called by dhcp-bridge when ip is released."""
    LOG.debug('Released IP |%s|' % address)
    fixed_ip = FixedIp.objects.get(address=address)
    if not fixed_ip.interface:
        raise Exception('IP %s released that is not associated' %
                        address)
    if not fixed_ip.leased:
        LOG.warn('IP %s released that was not leased', address)
    fixed_ip.update(dict(leased=False))

    if not fixed_ip.allocated:
        #TODO :update dhcp
        LOG.warn('IP |%s| leased that isn\'t allocated,need to update dhcp', address)


def get_dhcp_leases(network_ref):
    return linux_net.get_dhcp_leases(network_ref)


def delete_network(network_ref):

    domain = find_network(network_ref['libvirt_name'])
    if domain:
        LOG.warn('network %s exists, we will stop it' % network_ref['libvirt_name'])
        if domain.isActive():
            domain.destroy()
        domain.undefine()

        interface = conf['bond.name'] if network_ref.networktype == constant.BRIDGE else "vlan%s" % network_ref.vlanid
        if network_ref.networktype==constant.BRIDGE or network_ref.networktype == constant.VLAN:
            linux_net.remove_bridge(network_ref,interface)
            if network_ref.networktype == constant.VLAN:
                linux_net.remove_vlan(network_ref)


